(function (window, document, undefined) {
  function unique(arr) {
    var hash = {}, result = [];
    for (var i = 0, l = arr.length; i < l; ++i) {
      if (!hash.hasOwnProperty(arr[i])) { //it works with objects! in FF, at least
        hash[ arr[i] ] = true;
        result.push(arr[i]);
      }
    }
    return result;
  }

  var uniqueIdFeed = 0;

  function uniqueId() {
    return 'uid-' + new Date().getTime() + '-' + ++uniqueIdFeed;
  }

  function flash(target, complete) {
    target.pulse({ opacity: [0,1] }, { times: 3, duration: 200, complete: complete});
  }

  function scrollIntoView(object, callback) {
    if (object.length === 0) {
      return;
    }

    $('html,body').animate({scrollTop : object.first().offset().top - $(window).height() / 2}, 'slow', callback);
  }

  function notifyDynamicChanges(target) {
    flash(target, function() {
      target.attr("role", "alert");
      setTimeout(function() {
        target.removeAttr("role");
      }, 8000);
    });
  }

  function inform(message, form) {
    var msgBoard = form ? form.find('.user-message') : $('#page-message');
    if (msgBoard.length === 0) {
      msgBoard = $('.user-message:first');
    }

    if (msgBoard.length === 0) {
      alert(message.text);
      return;
    }

    msgBoard.empty();

    var msgBody = '';

    if (message.title) {
      msgBody += "<h6>" + message.title + "</h6>";
    }

    msgBody += '<p>' + message.text;

    if (message.action) {
      msgBody += ' <a href=\"' + message.action.url + '" title="' + message.action.title + '">' + message.action.text + '</a>';
    }

    msgBody += '</p>';

    msgBoard.append(msgBody);

    msgBoard.removeClass('invisible success notice error').addClass(message.type);

    scrollIntoView(msgBoard, function() {
      msgBoard.focus();
    });

    notifyDynamicChanges(msgBoard);
  }

  function clearValidationErrors(form) {
    form.find("[data-role='fieldcontain']").removeClass('invalid-field');
    form.find(".field-error").remove();
  }

  function enableSubmitButton(form) {
    if (!form) {
      return;
    }

    form.find('.awaiting-response').removeAttr('disabled').removeClass('awaiting-response');
  }

  function disableSubmitButton(form) {
    setTimeout(function () {
      form.find('[type="submit"]').attr('disabled', 'disabled').addClass('awaiting-response');
    }, 0);
  }

  var onModalClosed = null;

  function runAfterClosingModal(task, isInModal) {
    if (isInModal) {
      onModalClosed = task;
      //TODO UI close the modal dialog
    }
    else {
      task();
    }
  }

  function handleGenericJSONResponse(json, form) {
    if (!json.data) {
      enableSubmitButton(form);
      return;
    }

    var data = json.data;

    if (json.type === 'redirect') {
      window.location = data.url;
      return;
    }

    if (data.message) {
      inform(data.message, form);
    }
    else if (data.text) {
      inform(data, form);
    }

    enableSubmitButton(form);
  }

  function handleAjaxError(textStatus, form) {
    inform({ text: "Unable to perform request (" + textStatus + ")", type: 'error' }, form);
    enableSubmitButton(form);
  }

  function handleValidationErrors(form, errors) {
    clearValidationErrors(form);

    for (var i = 0; i < errors.length; i++) {
      var error = errors[i];
      var fieldContainer = form.find("[name='" + error.property + "']").closest("[data-role='fieldcontain']").addClass('invalid-field');

      for (var j = 0; j < error.messages.length; j++) {
        var message = error.messages[j];
        fieldContainer.append('<span class="field-error" role="alert">' + message + '</span>');
      }
    }

    form.find('.invalid-field:first label').focus();

    enableSubmitButton(form);
  }

  function handleModalValidationErrors(formid, errors) {
    handleValidationErrors(formid, errors);
    //TODO UI
  }

  var jsonResponseHandlers = {
  };

  function sendSmartJsonRequest(formEl) {
    var form = $(formEl);
    var isInModal = false;//TODO UI

    disableSubmitButton(form);

    $.ajax({
      type: form.attr('method'),
      url: form.attr('action').substring('1'),//all json forms' actions should start with '#', so that they will only work when javascript is enabled
      data: form.serialize(),
      success: function(msg) {
        if (Recaptcha.get_challenge() && form.find('.captcha').length > 0) {
          Recaptcha.reload('t');//pass 't' to prevent Recaptcha from messing with the focus
        }

        var json = JSON.parse(msg);
        var data = json.data;

        var customHandler = form.attr('data-json-handler-' + json.type);
        if (customHandler !== undefined) {
          jsonResponseHandlers[customHandler](form, json, isInModal);
          return;
        }

        if (json.type === 'errors') {
          if (isInModal) {
            handleModalValidationErrors(form, data.errors);
          } else {
            handleValidationErrors(form, data.errors);
          }

          return;
        }

        clearValidationErrors(form);

        runAfterClosingModal(function () {
          handleGenericJSONResponse(json, form);
        }, isInModal);
      },
      error : function (request, textStatus) {
        runAfterClosingModal(function () {
          handleAjaxError(textStatus, form);
        }, isInModal);
      }
    });

    return false;
  }

  //Each feature initialization function will be passed a DOM element (on which initializeFeatures is called). *Notice* it may *not* be the same element where the feature is declared (via data-features).
  var featureInitializations = {
    'user-message': function (container) {
      container.find('.user-message, #page-message').each(function() {
        var msgBoard = $(this);
        if (msgBoard.hasClass('invisible')) {
          return;
        }

        flash(msgBoard, null);

        msgBoard.find('.ajax-post').click(function () {
          $.ajax({
            type: 'post',
            url: $(this).attr('href'),
            success:function(msg) {
              var json = JSON.parse(msg);
              handleGenericJSONResponse(json, null);
            },
            error: function (request, textStatus) {
              handleAjaxError(textStatus, null);
            }
          });
          return false;
        });
      });
    },
    'accordion': function (container) {
      var expendedClass = 'content-expanded';

      container.find('.accordion').each(function() {
        $(this).find('a.selected').each(function () {
          var link = $(this);
          var container = link.closest('div, ul');

          if (container.hasClass('accordion-content')) {
            container.prev().addClass(expendedClass);
            return;
          }

          link.removeClass('selected');
          container.addClass('top-level-selected');
        });

        new Accordion(this.id, 3, expendedClass);
      });
    },
    'plan-selector': function(container) {
      var duration = 0;
      container.find('.plan-selector > select').bind('change',
              function () {
                var plans = $(this);
                var plan = $(this.options[this.selectedIndex]);
                var img = plans.parent().find('img');
                var src = img.attr('src');
                img.attr('alt', plan.html()).attr('src', src.substring(0, src.lastIndexOf('/') + 1) + plan.attr('data-icon-key') + '.png');

                var licenseId = plan.attr('value');
                var manualLicenseIds = plans.attr('data-manual-license-ids').split(',');
                for (var i = 0; i < manualLicenseIds.length; ++i) {
                  if (licenseId === manualLicenseIds[i]) {
                    $('#upgrade-confirmation').slideUp(duration);
                    $('#manual-license-notice').slideDown(duration);
                    return;
                  }
                }

                $('#upgrade-confirmation').slideDown(duration);
                $('#manual-license-notice').slideUp(duration);

                var notForEval = plans.closest('form').find('.not-for-eval');

                if (licenseId === plans.attr('data-eval-license-id')) {
                  notForEval.slideUp(duration);
                }
                else {
                  notForEval.slideDown(duration);
                }
              }).trigger('change');

      duration = 500;
    },
    'inline-help': function (container) {
      container.find('.inline-help').tipTip({
        maxWidth: '360px',
        attribute: 'data-help-content',
        //hoverActivation: true,
        enter: function (contentHolder) {
          contentHolder.content = '<h6>' + this.html() + '</h6>' + contentHolder.content;
        }
      });
    },
    'dialog-title-inline-help-fix': function(container) {
      container.find('.inline-help').bind('click', function() {
        this.focus();
      });
    },
    'smart-json-form': function (container) {
      container.find('.smart-json-form').submit(function () {
        sendSmartJsonRequest(this);
        return false;
      });
    },
    'captcha': function (container) {
      container.find('.captcha').each(function() {
        var id = uniqueId();
        var recaptcha = $(this);
        recaptcha.attr('id', id);
        recaptcha.find('.recaptcha_image').attr('id', 'recaptcha_image');

        Recaptcha.create(recaptcha.attr('data-recaptcha-key'), id, { theme: 'custom' });
      });
    },
    'gravatar-link': function (container) {
      container.find('.gravatar-link').click(function () {
        var link = $(this);
        var f = document.createElement('form');
        f.action = link.attr('href');
        f.method = 'post';

        var h = document.createElement('input');
        h.type = 'hidden';
        h.name = 'user';
        h.value = link.siblings('[name="gravatar"]').val();
        f.appendChild(h);

        document.body.appendChild(f);
        f.submit();

        return false;
      });
    },
    'with-custom-settings': function (container) {
      var duration = 0;

      container.find('.custom-settings').prev('.field').find('select').bind('change',
              function () {
                var customSettings = $(this).closest('form').find('.custom-settings');

                if (this.value === 'custom') {
                  customSettings.slideDown(duration);
                }
                else {
                  customSettings.slideUp(duration);
                }
              }).trigger('change');

      duration = 500;
    },
    'radio-button-accordion': function (container) {
      container.find('.radio-button-accordion').each(function () {
        var items = $(this).find('input');

        var duration = 0;

        items.bind('change',
                function () {
                  items.each(function () {
                    var content = $(this).siblings('div');
                    if (this.checked) {
                      content.slideDown(duration);
                    }
                    else {
                      content.slideUp(duration);
                    }
                  });
                }).trigger('change');

        duration = 500;
      });
    },
    'credit-card-bound': function (container) {
      container.find('.credit-card-bound').each(function () {
        var selectEl = this;

        function refreshCardInfo() {
          $('#payment-option-value > option').html($(selectEl.options[selectEl.selectedIndex]).attr('data-card'));
        }

        $(this).bind('change', refreshCardInfo);

        refreshCardInfo();
      });
    },
    'subdomain-checked': function(container) {
      container.find('[data-subdomain-checker-url]').bind('change', function () {
        var field = $(this);
        var form = field.closest('form');
        var original = form.attr('action');
        form.attr('action', '#' + field.attr('data-subdomain-checker-url'));
        sendSmartJsonRequest(form);
        form.attr('action', original);
      });
    },
    'link-button-preferred': function(container) {
      container.find('.link-button-preferred').each(function () {
        var btn = $(this);
        var link = document.createElement('A');
        link.href = 'javascript:void(0)';
        link.innerHTML = btn.html();
        btn.parent().append(link);
        btn.remove();
        $(link).click(function() {
          var target = $(this);
          setTimeout(function() {
            //timeout is for IE6
            target.closest('form').submit();
          }, 0);
        });
      });
    },
    'modal-dialog': function(container) {
      container.find('.modal-dialog-trigger').bind('click', function () {
        var link = $(this);
        var url = link.attr('href');
        if (url == undefined) {
          url = link.attr('data-href');
        }
        var dialog = $('div[data-target="' + url + '"]');
        if (dialog.length === 0) {
          var dialogDiv = document.createElement('div');
          document.body.appendChild(dialogDiv);
          dialog = $(dialogDiv).attr('data-target', url);
        }

        dialog.load(url, function (responseText, textStatus) {
          if (textStatus !== 'success' && textStatus !== 'notmodified') {
            alert('Oops, something went wrong!\nPlease refresh the page and try again.');
            return;
          }

          var heading = dialog.find('h1');
          heading.remove();
          dialog.dialog({
            modal: true,
            width: link.attr('data-dialog-width'),
            minHeight: 0,
            title: heading.html()
          }).parent().initializeFeatures();
        });

        return false;
      });
    },
    'close-dialog': function(container) {
      container.find('.close-dialog').click(function () {
        $(this).closest('.ui-dialog-content').dialog('close');
      });
    },
    'ajax-form-update': function(container) {
      container.find('[data-update-target-id]').bind('submit', function () {
        var form = $(this);
        $('#' + form.attr('data-update-target-id')).parent().load(form.attr('action'), form.serializeArray(),
                function (responseText, textStatus) {
                  if (textStatus !== 'success' && textStatus !== 'notmodified') {
                    alert('Oops, something went wrong!\nPlease refresh the page and try again.');
                    return;
                  }

                  var updateTargetContainer = $(this);
                  if (updateTargetContainer.closest('.ui-dialog').length > 0) {
                    updateTargetContainer.find('h1').remove();
                  }

                  updateTargetContainer.initializeFeatures();
                });
        return false;
      });
    },
    'ajax-load': function(container) {
      container.find('[data-update-target-container-id]').bind('click', function() {
        var link = $(this);
        $('#' + link.attr('data-update-target-container-id')).addClass('loading').
                load(link.attr('href'), function(responseText, textStatus) {
          if (textStatus !== 'success' && textStatus !== 'notmodified') {
            alert('Oops, something went wrong!\nPlease refresh the page and try again.');
            return;
          }

          var updateTargetContainer = $(this);
          updateTargetContainer.removeClass('loading').initializeFeatures();
          scrollIntoView(updateTargetContainer, null);
        });
        return false;
      });
    },
    'embed-tables': function(container) {
      var tables = container.find('.embed-tables').removeClass('no-script');
      var triggers = tables.find('tbody > [data-row-id] > td:last-child');

      function moreOrLess(trigger) {
        var id = trigger.closest('tr').attr('data-row-id');
        var childRows = trigger.closest('table').find('tr[data-parent-row-id=' + id + ']');
        if (trigger.hasClass('more')) {
          childRows.hide();
          return;
        }

        childRows.show();
      }

      triggers.each(
              function () {
                moreOrLess($(this));
              }).
              click(function() {
        var trigger = $(this);

        if (trigger.hasClass('inactive')) {
          return;
        }

        if (trigger.hasClass('more')) {
          trigger.removeClass('more').addClass('less');
        }
        else {
          trigger.removeClass('less').addClass('more');
        }

        moreOrLess(trigger);
      });

      tables.find('.more-trigger').click(function() {
        var trigger = $(this).closest('tr').find('td:last-child');
        if (trigger.hasClass('more')) {
          trigger.removeClass('inactive');
          trigger.trigger('click');
        }
      });
    },
    'zipcode-switcher': function(container) {
      var duration = 0;
      container.find('#country-value').change(
              function() {
                if (this.value === 'United States') {
                  $('#zipcode-field').slideDown(duration);
                }
                else {
                  $('#zipcode-field').slideUp(duration);
                }
              }).trigger('change');
      duration = 500;
    },
    'append-select-option': function(container) {
      container.find('[data-option-name]').each(function () {
        var info = $(this);
        var select = document.getElementById(info.attr('data-select-id'));
        var option = document.createElement('option');
        option.value = info.attr('data-option-value');
        option.innerHTML = info.attr('data-option-name');
        select.appendChild(option);
        option.selected = true;
      });
    },
    'redirect-on-closing-dialog': function(container) {
      container.find('[data-redirect-url-on-closing-dialog]').each(function() {
        var target = $(this);
        target.closest('.ui-dialog-content').bind('dialogclose', function() {
          window.location = target.attr('data-redirect-url-on-closing-dialog');
          window.location.reload();//we need to reload the page manually because we only changed url hash above
        });
      });
    },
    'ui-buttons': function(container) {
      container.find('.ui-button').button();
    },
    'dropdown-menus': function(container) {
      container.find('.utb-dropdown-menu').hoverIntent({
        timeout: 300,
        over: function() {
          $(this).addClass('hover');
        },
        out: function () {
          $(this).removeClass('hover');
        }
      });
    }
  };

  $.fn.initializeFeatures = function() {
    var containerFeatures = this.attr('data-features');

    //We aggregate all features to avoid duplication initialization. The price is that all featureInitialization functions will be called on the outmost DOM element (the element on which initializeFeatures is called) instead the ones on which data-features attribute is declared.
    //We trade it for code maintainability. Besides, it might even be faster than having to check and exclude features already initialized on the ancestor nodes
    var allFeatures = containerFeatures === undefined ? '' : containerFeatures;

    this.find('[data-features]').each(function () {
      allFeatures += ' ' + $(this).attr('data-features');
    });

    allFeatures = $.trim(allFeatures + ' user-message').replace(/ +/, ' ');//user message is the defaut feature

    var featureArray = unique(allFeatures.split(' '));

    for (var i = 0, len = featureArray.length; i < len; ++i) {
      var feature = featureArray[i];
      featureInitializations[feature](this);
    }

    return this;
  };

  $(function() {
    $(document.documentElement).initializeFeatures();
  });
})(this, this.document);

var redirect = function(url, selector, inseconds) {
  if (inseconds === 0) {
    window.location = url;
  } else {
    $(selector).empty();
    $(selector).append(inseconds);
    setTimeout(function() {
      redirect(url, selector, inseconds - 1);
    }, 1000);
  }
};